package com.ymh.lock;

import org.springframework.util.StringUtils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.util.Collections;

/**
 * 使用Redis单实例实现分布式锁
 * Created by aivin on 2018/8/1.
 */
public class RedisDistributedLock {

    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";
    private static final Long RELEASE_SUCCESS = 1L;

    /**
     * 校验参数
     *
     * @param jedisPool
     * @param lockKey
     * @param requestId
     * @param expireTime
     */
    private static void validParam(JedisPool jedisPool, String lockKey, String requestId, int expireTime) {

        if (null == jedisPool) {
            throw new IllegalArgumentException("jedisPool obj is null");
        }

        if (StringUtils.isEmpty(lockKey)) {
            throw new IllegalArgumentException("Lock key is null");
        }

        if (StringUtils.isEmpty(requestId)) {
            throw new IllegalArgumentException("requestId is blank");
        }

        if (expireTime < 0) {
            throw new IllegalArgumentException("expireTime is not allowed less zero");
        }
    }

    /**
     * 非阻塞式加锁
     *
     * @param jedisPool
     * @param lockKey
     * @param requestId
     * @param expireTime
     * @return
     */
    public static boolean tryLock(JedisPool jedisPool, String lockKey, String requestId, int expireTime) {
        validParam(jedisPool, lockKey, requestId, expireTime);
        Jedis jedis = null;
        try {

            jedis = jedisPool.getResource();
            String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
            if (LOCK_SUCCESS.equals(result)) {
                //加锁成功
                return true;
            }
        } catch (Exception e) {
            throw e;
        } finally {
            //关闭jedis客户端
            if (null != jedis) {
                jedis.close();
            }
        }
        return false;
    }

    /**
     * 阻塞式加锁
     *
     * @param jedisPool
     * @param lockKey
     * @param requestId
     * @param expireTime
     * @return
     */
    public static void lock(JedisPool jedisPool, String lockKey, String requestId, int expireTime) {
        validParam(jedisPool, lockKey, requestId, expireTime);
        if (tryLock(jedisPool, lockKey, requestId, expireTime)) {
            return;
        }
        //让线程休眠一段时间
        waitForLock();
        //递归调用自己进行加锁
        lock(jedisPool, lockKey, requestId, expireTime);
    }

    /**
     * 让当前线程休眠一段时间
     */
    private static void waitForLock() {
        try {
            Thread.currentThread().sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 解锁
     *
     * @param jedisPool
     * @param lockKey
     * @param requestId
     * @return
     */
    public static boolean unLock(JedisPool jedisPool, String lockKey, String requestId) {
        validParam(jedisPool, lockKey, requestId, 1);

        String script = "if redis.call('get',KEYS[1]) == ARGV[1] " +
                "then return redis.call('del',KEYS[1]) else return 0 end";

        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();

            Object object = jedis.eval(script,
                    Collections.singletonList(lockKey),
                    Collections.singletonList(requestId));

            if (RELEASE_SUCCESS.equals(object)) {
                return true;
            }

        } catch (Exception e) {
            throw e;
        } finally {
            if (null != jedis) {
                jedis.close();
            }
        }
        return false;
    }

}
